package pl.allegro.tech.hermes.integrationtests;

import static org.awaitility.Awaitility.waitAtMost;
import static pl.allegro.tech.hermes.frontend.FrontendConfigurationProperties.AUTH_PASSWORD;
import static pl.allegro.tech.hermes.frontend.FrontendConfigurationProperties.AUTH_USERNAME;
import static pl.allegro.tech.hermes.frontend.FrontendConfigurationProperties.FRONTEND_AUTHENTICATION_ENABLED;
import static pl.allegro.tech.hermes.frontend.FrontendConfigurationProperties.FRONTEND_AUTHENTICATION_MODE;
import static pl.allegro.tech.hermes.frontend.FrontendConfigurationProperties.FRONTEND_SSL_ENABLED;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.Map;
import java.util.stream.Stream;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.http.HttpHeaders;
import org.springframework.test.web.reactive.server.WebTestClient;
import pl.allegro.tech.hermes.api.Topic;
import pl.allegro.tech.hermes.integrationtests.setup.HermesExtension;
import pl.allegro.tech.hermes.test.helper.builder.TopicBuilder;
import pl.allegro.tech.hermes.test.helper.message.TestMessage;
import pl.allegro.tech.hermes.utils.Headers;

public class TopicAuthorizationTest {

  private static final String USERNAME = "someUser";
  private static final String PASSWORD = "somePassword123";
  private static final String MESSAGE = TestMessage.of("hello", "world").body();
  private static final String USERNAME2 = "foobar";

  @RegisterExtension
  public static final HermesExtension hermes =
      new HermesExtension()
          .withFrontendProfile("authNonRequired")
          .withFrontendProperty(AUTH_USERNAME, USERNAME)
          .withFrontendProperty(AUTH_PASSWORD, PASSWORD)
          .withFrontendProperty(FRONTEND_SSL_ENABLED, false)
          .withFrontendProperty(FRONTEND_AUTHENTICATION_MODE, "pro_active")
          .withFrontendProperty(FRONTEND_AUTHENTICATION_ENABLED, true);

  @ParameterizedTest
  @MethodSource("publishWhenAuthenticatedTopics")
  public void shouldPublishWhenAuthenticated(Topic topic) {
    // given
    hermes.initHelper().createTopic(topic);

    waitAtMost(Duration.ofSeconds(10))
        .untilAsserted(
            () -> {
              // when
              WebTestClient.ResponseSpec response =
                  hermes
                      .api()
                      .publish(
                          topic.getQualifiedName(),
                          MESSAGE,
                          createAuthorizationHeader(USERNAME, PASSWORD));

              // then
              response.expectStatus().isCreated();
            });
  }

  static Stream<Topic> publishWhenAuthenticatedTopics() {
    return Stream.of(
        TopicBuilder.topicWithRandomName().build(),
        TopicBuilder.topicWithRandomName().withPublisher(USERNAME).withAuthEnabled().build(),
        TopicBuilder.topicWithRandomName()
            .withPublisher(USERNAME)
            .withPublisher(USERNAME2)
            .withAuthEnabled()
            .build(),
        TopicBuilder.topicWithRandomName()
            .withPublisher(USERNAME)
            .withAuthEnabled()
            .withUnauthenticatedAccessDisabled()
            .build(),
        TopicBuilder.topicWithRandomName()
            .withPublisher(USERNAME)
            .withPublisher(USERNAME2)
            .withAuthEnabled()
            .withUnauthenticatedAccessDisabled()
            .build());
  }

  @ParameterizedTest
  @MethodSource("publishAsGuestWhenAuthIsNotRequiredTopics")
  public void shouldPublishAsGuestWhenAuthIsNotRequired(Topic topic) {
    // given
    hermes.initHelper().createTopic(topic);

    waitAtMost(Duration.ofSeconds(10))
        .untilAsserted(
            () -> {
              // when
              WebTestClient.ResponseSpec response =
                  hermes.api().publish(topic.getQualifiedName(), MESSAGE);

              // then
              response.expectStatus().isCreated();
            });
  }

  static Stream<Topic> publishAsGuestWhenAuthIsNotRequiredTopics() {
    return Stream.of(
        TopicBuilder.topicWithRandomName().build(),
        TopicBuilder.topicWithRandomName().withAuthEnabled().build(),
        TopicBuilder.topicWithRandomName().withPublisher(USERNAME2).withAuthEnabled().build());
  }

  @ParameterizedTest
  @MethodSource("notPublishAsGuestWhenAuthIsRequiredTopics")
  public void shouldNotPublishAsGuestWhenAuthIsRequired(Topic topic) {
    // given
    hermes.initHelper().createTopic(topic);

    waitAtMost(Duration.ofSeconds(10))
        .untilAsserted(
            () -> {
              // when
              WebTestClient.ResponseSpec response =
                  hermes.api().publish(topic.getQualifiedName(), MESSAGE);

              // then
              response.expectStatus().isForbidden();
            });
  }

  static Stream<Topic> notPublishAsGuestWhenAuthIsRequiredTopics() {
    return Stream.of(
        TopicBuilder.topicWithRandomName()
            .withAuthEnabled()
            .withUnauthenticatedAccessDisabled()
            .build(),
        TopicBuilder.topicWithRandomName()
            .withPublisher(USERNAME2)
            .withAuthEnabled()
            .withUnauthenticatedAccessDisabled()
            .build());
  }

  @ParameterizedTest
  @MethodSource("notPublishWithoutPermissionWhenAuthenticatedTopics")
  public void shouldNotPublishWithoutPermissionWhenAuthenticated(Topic topic) {
    // given
    hermes.initHelper().createTopic(topic);

    waitAtMost(Duration.ofSeconds(10))
        .untilAsserted(
            () -> {
              // when
              WebTestClient.ResponseSpec response =
                  hermes
                      .api()
                      .publish(
                          topic.getQualifiedName(),
                          MESSAGE,
                          createAuthorizationHeader(USERNAME, PASSWORD));

              // then
              response.expectStatus().isForbidden();
            });
  }

  static Stream<Topic> notPublishWithoutPermissionWhenAuthenticatedTopics() {
    return Stream.of(
        TopicBuilder.topicWithRandomName().withAuthEnabled().build(),
        TopicBuilder.topicWithRandomName().withPublisher(USERNAME2).withAuthEnabled().build(),
        TopicBuilder.topicWithRandomName()
            .withAuthEnabled()
            .withUnauthenticatedAccessDisabled()
            .build(),
        TopicBuilder.topicWithRandomName()
            .withPublisher(USERNAME2)
            .withAuthEnabled()
            .withUnauthenticatedAccessDisabled()
            .build());
  }

  private static HttpHeaders createAuthorizationHeader(String username, String password) {
    String credentials = username + ":" + password;
    Map<String, String> headers =
        Map.of(
            "Authorization",
            "Basic "
                + Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8)));
    return Headers.createHeaders(headers);
  }
}
